﻿using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace Beeflys
{
    sealed class ActorFactory : IDisposable
    {
        internal ActorFactory(ActorId id)
        {
            this.self = id;
        }

        private ActorId self;
        private Dictionary<string, IActorRef> actors = new Dictionary<string, IActorRef>();

        public int Count => this.actors.Count;

        public async Task<IActorRef> AddActorAsync(Type type, string host, string name)
        {
            if (string.IsNullOrEmpty(name))
            {
                throw new ArgumentNullException(nameof(name));
            }

            if (this.actors.ContainsKey(name))
            {
                throw new Exception("Duplicate names found :" + name);
            }

            var actorRef = this.CreateActor(type, host, name);
            this.actors.Add(name, actorRef);
            await actorRef.CallAsync<EmptyNotify>(Notify.Awake, TimeSpan.Zero, null);

            return actorRef;
        }

        public IEnumerable<IActorRef> GetActors()
        {
            return new List<IActorRef>(this.actors.Values);
        }

        public Task<IActorRef> GetActorAsync(string name)
        {
            if (!this.actors.ContainsKey(name))
            {
                return Task.FromResult<IActorRef>(null);
            }
            
            return Task.FromResult<IActorRef>(this.actors[name]);
        }

        public async Task<bool> DelActorAsync(string name)
        {
            if (!this.actors.ContainsKey(name))
            {
                return false;
            }

            var actorRef = this.actors[name];
            this.actors.Remove(name);
            
            await actorRef.CallAsync<EmptyNotify>(Notify.Destroy, TimeSpan.Zero, null);
            return true;
        }

        private AActorRef CreateActor(Type type, string host, string name)
        {
            var id = this.CreateId(host, name);
            var actor = Activator.CreateInstance(type) as AActor;
            actor.Constructor(id);

            if (string.IsNullOrEmpty(host) || host == ActorId.DEFAULT_HOST)
            {
                return new EntityRef(actor, id);
            }
            else
            {
                return new FabledRef(actor, id);
            }
        }

        private ActorId CreateId(string host, string name)
        {
            string actorHost = string.IsNullOrEmpty(host) ? ActorId.DEFAULT_HOST : host;
            string actorTree = (this.self == null) ? string.Empty : this.self.FullName;
            string actorName = string.IsNullOrEmpty(name) ? Guid.NewGuid().ToString() : name;
            
            return ActorId.Create(actorHost, actorTree, actorName);
        }

        public void Dispose()
        {
            foreach (var item in this.actors)
            {
                this.DelActorAsync(item.Key).NoAwait();
            }

            this.actors.Clear();
        }
    }
}